#include "touch_calibration.h"
#include <unistd.h>
#include <QPainter>
#include <QFile>
#include <QTimer>
#include <QApplication>
#include <QDesktopWidget>
#include <QMouseEvent>
#include <QScreen>
#include <QTextStream>
#include <QDebug>

//zhaolunhan
touch_calibration::touch_calibration(QWidget* parent)
: /*modal_base_dialog(parent, Qt::FramelessWindowHint),*/
  _pressCount(0), _press_timer(NULL),
  _draw_timer(NULL), /*_monitor_client(NULL),*/
  _which(-1), _average_index(0), _total_num(0),
  _calibration_ret(true)
{
    set_ui(this);

    QDesktopWidget* desktopWidget = QApplication::desktop();
    QRect screenRect = desktopWidget->screenGeometry();
    setFixedSize(QSize(screenRect.width(), screenRect.height()));

    setFocusPolicy(Qt::StrongFocus);
    setFocus();
    setModal(true);

    double width = qt_screen->deviceWidth();
    double height = qt_screen->deviceHeight();

    double dx = width / 16;
    double dy = height / 16;

    if(qt_screen->deviceWidth() > 1024)
    {
        dx = width / 20;
        dy = height / 18;
    }

    QPoint* points = _data.screenPoints;
    QPoint* former_points = _former_data.screenPoints;

    //never rotation
    if(qt_screen->transformOrientation() == 0)
    {
        points[QWSPointerCalibrationData::TopLeft] = QPoint(dx, dy);
        points[QWSPointerCalibrationData::BottomLeft] = QPoint(dx, height - dy);
        points[QWSPointerCalibrationData::BottomRight] = QPoint(width - dx, height - dy);
        points[QWSPointerCalibrationData::TopRight] = QPoint(width - dx, dy);
        points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        former_points[QWSPointerCalibrationData::TopLeft] = QPoint(dx, dy);
        former_points[QWSPointerCalibrationData::BottomLeft] = QPoint(dx, height - dy);
        former_points[QWSPointerCalibrationData::BottomRight] = QPoint(width - dx, height - dy);
        former_points[QWSPointerCalibrationData::TopRight] = QPoint(width - dx, dy);
        former_points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        double t_b_dy = (height - dy - dy) / MCGS_CALIBRATION_SCREEN_POINTS;
        double t_b_dx = (width - dx - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dx = (width / 2 - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dy = (height / 2 - dy) / MCGS_CALIBRATION_SCREEN_POINTS;

        for(int i = 0, n = MCGS_CALIBRATION_SCREEN_POINTS - 1; i < MCGS_CALIBRATION_SCREEN_POINTS; ++i, --n)
        {
            _other_screen_points[QWSPointerCalibrationData::TopLeft][i] = QPoint(dx, height - dy - n * t_b_dy);
            _other_screen_points[QWSPointerCalibrationData::BottomLeft][i] = QPoint(width - dx - n * t_b_dx, height - dy);
            _other_screen_points[QWSPointerCalibrationData::BottomRight][i] = QPoint(width - dx, dy + n * t_b_dy);
            _other_screen_points[QWSPointerCalibrationData::TopRight][i] = QPoint(width / 2 + n * tr_ct_dx, height / 2 - n * tr_ct_dy);
        }
    }

    //clockwise rotation 90 degrees
    if(qt_screen->transformOrientation() == 1)
    {
        points[QWSPointerCalibrationData::TopRight] = QPoint(dx, dy);
        points[QWSPointerCalibrationData::TopLeft] = QPoint(dx, height - dy);
        points[QWSPointerCalibrationData::BottomLeft] = QPoint(width - dx, height - dy);
        points[QWSPointerCalibrationData::BottomRight] = QPoint(width - dx, dy);
        points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        former_points[QWSPointerCalibrationData::TopRight] = QPoint(dx, dy);
        former_points[QWSPointerCalibrationData::TopLeft] = QPoint(dx, height - dy);
        former_points[QWSPointerCalibrationData::BottomLeft] = QPoint(width - dx, height - dy);
        former_points[QWSPointerCalibrationData::BottomRight] = QPoint(width - dx, dy);
        former_points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        double t_b_dy = (height - dy - dy) / MCGS_CALIBRATION_SCREEN_POINTS;
        double t_b_dx = (width - dx - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dx = (width / 2 - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dy = (height / 2 - dy) / MCGS_CALIBRATION_SCREEN_POINTS;

        for(int i = 0, n = MCGS_CALIBRATION_SCREEN_POINTS - 1; i < MCGS_CALIBRATION_SCREEN_POINTS; ++i, --n)
        {
            _other_screen_points[QWSPointerCalibrationData::TopLeft][i] = QPoint(width - dx - n * t_b_dx, height - dy);
            _other_screen_points[QWSPointerCalibrationData::BottomLeft][i] = QPoint(width - dx, dy + n * t_b_dy);
            _other_screen_points[QWSPointerCalibrationData::BottomRight][i] = QPoint(dx + n * t_b_dx, dy);
            _other_screen_points[QWSPointerCalibrationData::TopRight][i] = QPoint(width / 2 - n * tr_ct_dx, height / 2 - n * tr_ct_dy);
        }
    }

    //clockwise rotation 180 degrees
    if(qt_screen->transformOrientation() == 2)
    {
        points[QWSPointerCalibrationData::BottomRight] = QPoint(dx, dy);
        points[QWSPointerCalibrationData::TopRight] = QPoint(dx, height - dy);
        points[QWSPointerCalibrationData::TopLeft] = QPoint(width - dx, height - dy);
        points[QWSPointerCalibrationData::BottomLeft] = QPoint(width - dx, dy);
        points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        former_points[QWSPointerCalibrationData::BottomRight] = QPoint(dx, dy);
        former_points[QWSPointerCalibrationData::TopRight] = QPoint(dx, height - dy);
        former_points[QWSPointerCalibrationData::TopLeft] = QPoint(width - dx, height - dy);
        former_points[QWSPointerCalibrationData::BottomLeft] = QPoint(width - dx, dy);
        former_points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        double t_b_dy = (height - dy - dy) / MCGS_CALIBRATION_SCREEN_POINTS;
        double t_b_dx = (width - dx - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dx = (width / 2 - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dy = (height / 2 - dy) / MCGS_CALIBRATION_SCREEN_POINTS;

        for(int i = 0, n = MCGS_CALIBRATION_SCREEN_POINTS - 1; i < MCGS_CALIBRATION_SCREEN_POINTS; ++i, --n)
        {
            _other_screen_points[QWSPointerCalibrationData::TopLeft][i] = QPoint(width - dx, dy + n * t_b_dy);
            _other_screen_points[QWSPointerCalibrationData::BottomLeft][i] = QPoint(dx + n * t_b_dx, dy);
            _other_screen_points[QWSPointerCalibrationData::BottomRight][i] = QPoint(dx, height- dy - n * t_b_dy);
            _other_screen_points[QWSPointerCalibrationData::TopRight][i] = QPoint(width / 2 - n * tr_ct_dx, height / 2 + n * tr_ct_dy);
        }
    }

    //clockwise rotation 270 degrees
    if(qt_screen->transformOrientation() == 3)
    {
        points[QWSPointerCalibrationData::BottomLeft] = QPoint(dx, dy);
        points[QWSPointerCalibrationData::BottomRight] = QPoint(dx, height - dy);
        points[QWSPointerCalibrationData::TopRight] = QPoint(width - dx, height - dy);
        points[QWSPointerCalibrationData::TopLeft] = QPoint(width - dx, dy);
        points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        former_points[QWSPointerCalibrationData::BottomLeft] = QPoint(dx, dy);
        former_points[QWSPointerCalibrationData::BottomRight] = QPoint(dx, height - dy);
        former_points[QWSPointerCalibrationData::TopRight] = QPoint(width - dx, height - dy);
        former_points[QWSPointerCalibrationData::TopLeft] = QPoint(width - dx, dy);
        former_points[QWSPointerCalibrationData::Center] = QPoint(width / 2, height / 2);

        double t_b_dy = (height - dy - dy) / MCGS_CALIBRATION_SCREEN_POINTS;
        double t_b_dx = (width - dx - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dx = (width / 2 - dx) / MCGS_CALIBRATION_SCREEN_POINTS;
        double tr_ct_dy = (height / 2 - dy) / MCGS_CALIBRATION_SCREEN_POINTS;

        for(int i = 0, n = MCGS_CALIBRATION_SCREEN_POINTS - 1; i < MCGS_CALIBRATION_SCREEN_POINTS; ++i, --n)
        {
            _other_screen_points[QWSPointerCalibrationData::TopLeft][i] = QPoint(dx + n * t_b_dx, dy);
            _other_screen_points[QWSPointerCalibrationData::BottomLeft][i] = QPoint(dx, height -dy - n * t_b_dy);
            _other_screen_points[QWSPointerCalibrationData::BottomRight][i] = QPoint(width - dx - n * t_b_dx, height - dy);
            _other_screen_points[QWSPointerCalibrationData::TopRight][i] = QPoint(width / 2 + n * tr_ct_dx, height / 2 + n * tr_ct_dy);
        }
    }

    _press_timer = new QTimer(this);
    connect(_press_timer, SIGNAL(timeout()), this, SLOT(probe_calibration()));

    _draw_timer = new QTimer(this);
    connect(_draw_timer, SIGNAL(timeout()), this, SLOT(draw_cross()));

    memset(_average_array, 0, sizeof(_average_array));

    QWSServer::mouseHandler()->getCalibration(&_former_data);
}

touch_calibration::~touch_calibration()
{}

int touch_calibration::calibration_exec()
{
    QWSServer::mouseHandler()->clearCalibration();
    grabMouse();
    activateWindow();
    int ret = exec();
    releaseMouse();

    return ret;
}

int touch_calibration::calibration_exec_without_clear()
{
    grabMouse();
    activateWindow();
    int ret = exec();
    releaseMouse();

    return ret;
}

void touch_calibration::paintEvent(QPaintEvent* event)
{
    QPainter p(this);

    QPoint point;
    if(_pressCount == 0)
    {
        point = _data.screenPoints[_pressCount];
    }
    else if(_pressCount >= 5)
    {
        calibration_confirm_ui(&p);
        return;
    }
    else
    {
        point = _other_screen_points[_pressCount - 1][_which];
    }

    //Map to logical coordinates in case the screen is transformed
    QSize screenSize(qt_screen->deviceWidth(), qt_screen->deviceHeight());
    point = qt_screen->mapFromDevice(point, screenSize);

    p.fillRect(event->rect(), Qt::white);

    if(qt_screen->deviceWidth() > 1024)
    {
        p.fillRect(point.x() - 30, point.y() - 1,  60, 2,  Qt::black);
        p.fillRect(point.x() - 1,  point.y() - 30, 2,  60, Qt::black);
        p.fillRect(point.x() - 1,  point.y() - 1,  2,  2,  Qt::white);
    }
    else
    {
        p.fillRect(point.x() - 20, point.y() - 1,  40, 2,  Qt::black);
        p.fillRect(point.x() - 1,  point.y() - 20, 2,  40, Qt::black);
        p.fillRect(point.x() - 1,  point.y() - 1,  2,  2,  Qt::white);
    }
}

void touch_calibration::mousePressEvent(QMouseEvent* event)
{
    if(_pressCount >= 5)
    {
        if(_calibration_ret)
        {
            QDesktopWidget* desktopWidget = QApplication::desktop();
            QRect screenRect = desktopWidget->screenGeometry();

            int width = screenRect.width();
            int height = screenRect.height();

            int dx = width / 2;
            int dy = height / 2;

            int ltx = dx - 140;
            int lty = dy - 45;
            int rbx = dx + 140;
            int rby = dy + 45;

            QRegion reg(QRect(ltx, lty, rbx - ltx, rby - lty));

            if(!reg.contains(event->pos()))
            {
                // Give up recent calibration instead of using former one.
                QWSServer::mouseHandler()->calibrate(&_former_data);
                sync();
            }
        }
        _pressCount = 0;
        accept();
    }
    else
    {
        _total_num = 0;
        _average_index = 0;

        //Map from device coordinates in case the screen is transformed
        QSize screenSize(qt_screen->width(), qt_screen->height());
        QPoint p = qt_screen->mapToDevice(event->pos(), screenSize);

        //We must record the press point as no any point reported from mouseMoveEvent
        _average_array[0] = p;

        _press_timer->start(1000);
    }
}

void touch_calibration::mouseReleaseEvent(QMouseEvent*)
{
    if(_pressCount >= 5)
    {
        // Assure that the last release happened in QWSCalibratedMouseHandler::sendFiltered.
        QWSServer::mouseHandler()->calibrate(&_data);
        sync();

        if(!linear_coefficient_verify())
        {
            // Give up recent calibration instead of using former one.
            QWSServer::mouseHandler()->calibrate(&_former_data);
            sync();
        }
    }

    _press_timer->stop();
}

void touch_calibration::mouseMoveEvent(QMouseEvent* event)
{
    //Map from device coordinates in case the screen is transformed
    QSize screenSize(qt_screen->width(), qt_screen->height());
    QPoint p = qt_screen->mapToDevice(event->pos(), screenSize);

    if(_average_index >= MCGS_CALIBRATION_PROBE_SET)
    {
        _average_index = 0;
    }
    _average_array[_average_index++] = p;
    _total_num++;
}

void touch_calibration::accept()
{
    QDialog::accept();
}

void touch_calibration::probe_calibration()
{
    _press_timer->stop();

    if(!_draw_timer->isActive())
    {
        if(_total_num < MCGS_CALIBRATION_PROBE_SET)
        {
            if(_total_num >= 1)
            {
                _data.devPoints[_pressCount] = _average_array[_total_num - 1];
            }
            else if(_total_num == 0)
            {
                _data.devPoints[_pressCount] = _average_array[_total_num];
            }
            else
            {}
        }
        else
        {
            QPoint sumP(0, 0);
            for(int i = 0; i < MCGS_CALIBRATION_PROBE_SET; i++)
            {
                sumP += _average_array[i];
            }
            _data.devPoints[_pressCount].setX(sumP.x() / MCGS_CALIBRATION_PROBE_SET);
            _data.devPoints[_pressCount].setY(sumP.y() / MCGS_CALIBRATION_PROBE_SET);
        }
        _total_num = 0;
        _average_index = 0;

        if(++_pressCount >= 5)
        {
            _step1->setText(QObject::trUtf8("新的校准设置已测定。"));
            _step2->setText(QObject::trUtf8("点击按钮内区域接受新设置；点击按钮外区域保留原有设置。"));

            repaint();
        }
        else
        {
            _which = -1;
            _draw_timer->start(MCGS_CALIBRATION_DRAWCROSS_TIMEOUT);
        }
    }
}

void touch_calibration::draw_cross()
{
    if(++_which >= MCGS_CALIBRATION_SCREEN_POINTS - 1)
    {
        _draw_timer->stop();
    }

    //construct refresh filed without whole filed update
    QPoint point;
    QPoint last_point;

    point = _other_screen_points[_pressCount - 1][_which];
    if(_which == 0)
    {
        last_point = _data.screenPoints[_pressCount - 1];
    }
    else
    {
        last_point = _other_screen_points[_pressCount - 1][_which - 1];
    }

    //Map to logical coordinates in case the screen is transformed
    QSize screenSize(qt_screen->deviceWidth(), qt_screen->deviceHeight());
    point = qt_screen->mapFromDevice(point, screenSize);
    last_point = qt_screen->mapFromDevice(last_point, screenSize);

    QRect repaint_filed;
    if(qt_screen->deviceWidth() > 1024)
    {
        repaint_filed = QRect(point.x() - 30, point.y() - 30,  60, 60).united(
                              QRect(last_point.x() - 30, last_point.y() - 30,  60, 60));
    }
    else
    {
        repaint_filed = QRect(point.x() - 20, point.y() - 20,  40, 40).united(
                              QRect(last_point.x() - 20, last_point.y() - 20,  40, 40));
    }

    repaint(repaint_filed);
}

void touch_calibration::set_ui(QDialog* dlg)
{
    //Note: on X11 you also have to pass Qt::X11BypassWindowManagerHint for this flag to work correctly.
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);

    _grid_layout = new QGridLayout(dlg);
    _grid_layout->setObjectName(QString::fromUtf8("_grid_layout"));
    _v_layout = new QVBoxLayout();
    _v_layout->setObjectName(QString::fromUtf8("_v_layout"));
    _h_layout1 = new QHBoxLayout();
    _h_layout1->setObjectName(QString::fromUtf8("_h_layout"));
    _h_layout1->setContentsMargins(0, 20, 0, 0);
    _h_spacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

    _h_layout1->addItem(_h_spacer1);

    _step1 = new QLabel(dlg);
    _step1->setObjectName(QString::fromUtf8("_step1"));
    _step1->setAutoFillBackground(false);
    _step1->setOpenExternalLinks(false);

    _h_layout1->addWidget(_step1);

    _h_spacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

    _h_layout1->addItem(_h_spacer2);


    _v_layout->addLayout(_h_layout1);

    _h_layout2 = new QHBoxLayout();
    _h_layout2->setObjectName(QString::fromUtf8("horizontalLayout_2"));
    _h_spacer3 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

    _h_layout2->addItem(_h_spacer3);

    _step2 = new QLabel(dlg);
    _step2->setObjectName(QString::fromUtf8("_step2"));

    _h_layout2->addWidget(_step2);

    _h_spacer4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

    _h_layout2->addItem(_h_spacer4);


    _v_layout->addLayout(_h_layout2);

    _v_spacer1 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);

    _v_layout->addItem(_v_spacer1);

    _v_spacer2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);

    _v_layout->addItem(_v_spacer2);

    _v_spacer3 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);

    _v_layout->addItem(_v_spacer3);

    _grid_layout->addLayout(_v_layout, 0, 0, 7, 7);

    _step1->setText(QObject::trUtf8("将手指轻而准确地放置在十字光标的中心点，停留至少1秒时间。"));
    _step2->setText(QObject::trUtf8("当目标在屏幕上移动时，重复该动作。"));
}

void touch_calibration::calibration_confirm_ui(QPainter* painter)
{
    QDesktopWidget* desktopWidget = QApplication::desktop();
    QRect screenRect = desktopWidget->screenGeometry();

    int width = screenRect.width();
    int height = screenRect.height();

    int dx = width / 2;
    int dy = height / 2;

    int ltx = dx - 140;
    int lty = dy - 45;
    int rtx = dx + 140;
    int rty = lty;
    int lbx = ltx;
    int lby = dy + 45;
    int rbx = rtx;
    int rby = lby;

    painter->fillRect(rect(), Qt::white);
    painter->fillRect(QRect(ltx, lty, rbx - ltx, rby - lty), Qt::lightGray);

    QPen pen;
    pen.setStyle(Qt::SolidLine);
    pen.setColor(Qt::black);
    painter->setPen(pen);
    painter->drawLine(QPoint(ltx, lty), QPoint(rtx, rty));
    painter->drawLine(QPoint(ltx, lty), QPoint(lbx, lby));
    painter->drawLine(QPoint(rtx, rty), QPoint(rbx, rby));
    painter->drawLine(QPoint(lbx, lby), QPoint(rbx, rby));


    QFont font(painter->font());
    font.setPixelSize(painter->font().pixelSize() + 20);
    painter->setFont(font);
    font.setLetterSpacing(QFont::AbsoluteSpacing, 10);
    painter->drawText(QRect(ltx, lty, rbx - ltx, rby - lty), Qt::AlignCenter, QObject::trUtf8("确认校准"));
}

bool touch_calibration::linear_coefficient_verify()
{
    QString calFile = QString::fromLocal8Bit(qgetenv("POINTERCAL_FILE"));
    if(calFile.isEmpty())
    {
        calFile = QLatin1String("/etc/pointercal");
    }

    QFile file(calFile);
    if(file.open(QIODevice::ReadOnly))
    {
        int a, b, c, d, e, f, s;
        QTextStream t(&file);
        t >> a >> b >> c >> d >> e >> f >> s;
        //Note: s cannot be valued by 0 as transform need s to be divisor
        if(s == 0 || t.status() != QTextStream::Ok)
        {
            qCritical("Corrupt calibration data");
            _calibration_ret = false;
        }
    }
    else
    {
        qDebug() << "Could not read calibration: " << calFile;
    }

    return _calibration_ret;
}
